//el: 挂载节点的选择器字符串
//vm: MVVM的实例对象
function Compile(el, vm) {
    //this:Compile的实例对象
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);

    //确定挂载节点存在 进行模板解析
    if (this.$el) {
        //将挂载节点中的所有子元素统剪切到一个文档碎片中
        this.$fragment = this.node2Fragment(this.$el);
        //进行模板解析!!!!
        this.init();
        //挂载节点重新将解析完的模板挂载回来
        this.$el.appendChild(this.$fragment);
    }
}

Compile.prototype = {
    node2Fragment: function(el) {
        var fragment = document.createDocumentFragment(),
            child;

        //循环剪切我们挂载节点的第一个子元素
        //firstChild:考虑文本节点
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    },
    isDirective: function(attr) {
        return attr.indexOf('v-') == 0;
    },
    isEventDirective: function(dir) {
        return dir.indexOf('on') === 0;
    },
    isElementNode: function(node) {
        return node.nodeType == 1;
    },
    isTextNode: function(node) {
        return node.nodeType == 3;
    },




    //模板解析的入口方法
    init: function() {
        //this.$fragment:文档碎片 ==> 挂载节点的模板
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
        //el:文档碎片   childNodes:包含文本节点
        var childNodes = el.childNodes,
            me = this;

        //将childNodes伪数组转成真数组
        //Array.prototype.slice.call(childNodes)
        [].slice.call(childNodes).forEach(function(node) {
            //node : 模板上的一个个子节点
            var text = node.textContent;
            var reg = /\{\{(.*)\}\}/;

            if (me.isElementNode(node)) {
                //元素节点
                me.compile(node);
            } else if (me.isTextNode(node) && reg.test(text)) {
                //文本节点 & 文本内容中包含插值表达式
                // RegExp.$1 : 插值中的表达式
                me.compileText(node, RegExp.$1);
            }

            //模板的深度解析
            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node);
            }
        });
    },

    compile: function(node) {
        var nodeAttrs = node.attributes,
            me = this;

        [].slice.call(nodeAttrs).forEach(function(attr) {
            var attrName = attr.name;
            //判断是否以v-开头
            if (me.isDirective(attrName)) {
                var exp = attr.value;
                //把v-砍掉
                var dir = attrName.substring(2);
                //是不是以on开头
                if (me.isEventDirective(dir)) {
                    //事件指令解析
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                } else {
                    //其他指令解析
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                }

                node.removeAttribute(attrName);
            }
        });
    },

    //node:当前被解析的节点
    //exp: 表达式
    compileText: function(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    },
};

// 编译器
var compileUtil = {
    //node:当前被解析的节点
    //vm:当前vm实例对象
    //exp: 表达式
    text: function(node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },

    html: function(node, vm, exp) {
        this.bind(node, vm, exp, 'html');
    },

    model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');

        //实现数据双向绑定
        var me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            //如果在input中输入的值 和 原始的值不一样
            // a.b.c.d = newValue;
            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },

    class: function(node, vm, exp) {
        this.bind(node, vm, exp, 'class');
    },

    //给v-on来使用的
    //node: 指令所在的节点; vm:vm实例对象 ;exp:表达式 ；dir:指令(on:click)
    eventHandler: function(node, vm, exp, dir) {
        //eventType:click
        //fn:methods中exp指定的函数
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },


    //每一个指令都会调用的方法(v-on 除外)
    bind: function(node, vm, exp, dir) {

        var updaterFn = updater[dir + 'Updater'];

        //node:当前真正解析的节点
        //this._getVMVal(vm, exp): exp对应的值
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        //开始构建dep和watcher的多对多关系
        new Watcher(vm, exp, function(value, oldValue) {
            //updaterFn : 对应指令的更新器
            //node : 对应指令所在的节点
            //value:exp对应的最新的值
            //oldValue:exp对应的老值
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    //根据exp 去 vm实例对象上找exp对应的真正的值
    //exp: obj.msg.text
    _getVMVal: function(vm, exp) {
        //val ： data配置對象
        var val = vm._data;
        // [obj,msg,text]
        exp = exp.split('.');
        //k:表達式中的每一层
        exp.forEach(function(k) {
            //k:obj , msg , text
            //找值的过程中 会触发数据代理 数据劫持!!!!
            val = val[k];
        });
        return val;
    },

    //数据双向绑定
    _setVMVal: function(vm, exp, value) {
        var val = vm._data;
        exp = exp.split('.');
        // a.b.c.d
        //i: 0 1 2 3
        exp.forEach(function(k, i) {
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
    }
};

// 更新器
var updater = {
    //node:当前真正解析的节点
    //value: exp对应的值
    textUpdater: function(node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },

    htmlUpdater: function(node, value) {
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },

    //node:当前真正解析的节点
    //value: exp对应的值;初始化时是"big";响应式时是"small"
    //oldValue: 初始值为undefined ; 响应式时是:"big"
    classUpdater: function(node, value, oldValue) {
        //初始化:className:"red"
        //响应式时是:"red big"
        var className = node.className;
        //初始化:className:"red"
        //响应式时是:"red"
        className = className.replace(oldValue, '').replace(/\s$/, '');
        //初始化:space:' '
        //响应式时是:space:' '
        var space = className && String(value) ? ' ' : '';

        //初始化:"red big"
        //响应式时是:"red small"
        node.className = className + space + value;
    },

    modelUpdater: function(node, value, oldValue) {
        node.value = typeof value == 'undefined' ? '' : value;
    }
};